﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

using LowLevelGraphics.ColorSpaces;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Channel difference mode
    /// </summary>
    public enum CHANNELDIFFERENCE
    {
        /// <summary>
        /// Use added differences between R-G channel, G-B channel, B-R channel
        /// </summary>
        GRAY_SUB_RG_GB_BR,

        /// <summary>
        /// Use added differences between R-G channel and G-B channel
        /// </summary>
        GRAY_SUB_RG_GB,

        /// <summary>
        /// Use added differences between R-G channel G-B and B-R channel in color mode
        /// </summary>
        COLOR_RG_GB_BR,

        /// <summary>
        /// Multiply the channel differences
        /// </summary>
        GRAY_MUL_RG_GB_BR,

        /// <summary>
        /// divide channel differences
        /// </summary>
        GRAY_DIV_RG_GB_BR,

        /// <summary>
        /// use ratios of channel R-G, G-B, B-R
        /// </summary>
        GRAY_RATIO_RG_GB_BR
    }

    /// <summary>
    /// Use this class to show channel differences
    /// </summary>
    public class ChannelDifferences : BaseImageFilter
    {
        protected CHANNELDIFFERENCE M_CHANNELDIFFERENCE = CHANNELDIFFERENCE.GRAY_SUB_RG_GB_BR;
        protected bool m_bNormalize = true;

        protected AbstractColorSpace m_AbstractColorSpace = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="ChannelDifferences"/> class.
        /// </summary>
        public ChannelDifferences()
            : this(CHANNELDIFFERENCE.GRAY_SUB_RG_GB_BR, false)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ChannelDifferences"/> class.
        /// </summary>
        /// <param name="_channeldifference">The _channeldifference.</param>
        /// <param name="_bNormalize">if set to <c>true</c> [_b normalize].</param>
        public ChannelDifferences(CHANNELDIFFERENCE _channeldifference, bool _bNormalize)
        {
            M_CHANNELDIFFERENCE = _channeldifference;
            m_bNormalize = _bNormalize;
        }

        /// <summary>
        /// used by clone to create a deep copy
        /// </summary>
        /// <param name="_channelDifferences"></param>
        internal ChannelDifferences(ChannelDifferences _channelDifferences)
        {
            this.M_CHANNELDIFFERENCE = _channelDifferences.M_CHANNELDIFFERENCE;
            this.m_bNormalize = _channelDifferences.m_bNormalize;
        }

        /// <summary>
        /// Gets or sets channel difference mode
        /// </summary>
        public CHANNELDIFFERENCE ChannelDifferenc
        {
            get { return M_CHANNELDIFFERENCE; }
            set { M_CHANNELDIFFERENCE = value; }
        }

        /// <summary>
        /// Gets or sets the abstract color space.
        /// </summary>
        /// <value>The abstract color space.</value>
        public AbstractColorSpace AbstractColorSpace
        {
            get { return m_AbstractColorSpace; }
            set { m_AbstractColorSpace = value; }
        }

        /// <summary>
        /// Gets or sets whether this filter uses a normalizing function
        /// </summary>
        public bool Normalize
        {
            get { return m_bNormalize; }
            set { m_bNormalize = value; }
        }

        /// <summary>
        /// executes this filter
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            int nRG = 0;
            int nGB = 0;
            int nBR = 0;
            int nR = 0;
            int nG = 0;
            int nB = 0;
            int nValue = 0;

            UnsafeBitmap bitmap = _bitmap;
            Color color = Color.Transparent;

            int y = 0;
            int x = 0;

            for (y = 0; y < bitmap.Height; y++)
            {
                for (x = 0; x < bitmap.Width; x++)
                {
                    color = bitmap.GetPixel(x, y);

                    switch (M_CHANNELDIFFERENCE)
                    {
                        case CHANNELDIFFERENCE.COLOR_RG_GB_BR:
                            nR = Math.Abs(color.R - color.G);
                            nG = Math.Abs(color.G - color.B);
                            nB = Math.Abs(color.B - color.R);
                            break;

                        case CHANNELDIFFERENCE.GRAY_SUB_RG_GB_BR:
                            nValue = Math.Abs(color.R - color.G) + Math.Abs(color.G - color.B) + Math.Abs(color.B - color.R);
                            nR = nG = nB = nValue;
                            if (m_bNormalize) nValue /= 3;
                            break;

                        case CHANNELDIFFERENCE.GRAY_SUB_RG_GB:
                            nValue = Math.Abs(color.R - color.G) + Math.Abs(color.G - color.B);
                            nR = nG = nB = nValue;
                            if (m_bNormalize) nValue /= 2;
                            break;

                        case CHANNELDIFFERENCE.GRAY_MUL_RG_GB_BR:
                            nValue = color.R * color.G / 255 + color.G * color.B / 255 + color.B * color.R / 255;
                            if (m_bNormalize) nValue /= 3;
                            nR = nG = nB = nValue;
                            break;

                        case CHANNELDIFFERENCE.GRAY_RATIO_RG_GB_BR:
                            nValue = (int)((float)(color.R + 1) / (float)(color.G + 1) + (float)(color.G + 1) / (float)(color.B + 1) + (float)(color.B + 1) / (float)(color.R + 1));
                            nR = nG = nB = nValue;
                            break;

                        case CHANNELDIFFERENCE.GRAY_DIV_RG_GB_BR:
                            nValue = (int)((float)(color.R + 1) / (float)(color.G + 1) + (float)(color.G + 1) / (float)(color.B + 1) + (float)(color.B + 1) / (float)(color.R + 1));
                            if (m_bNormalize)
                            {
                                //nmin is 3, nmax is 256, so it's ok to normalize like this
                                if (nValue > 255) nValue = 255;
                            }
                            nR = nG = nB = nValue;
                            break;
                    }
                    
                    if (nR > 255) nR = 255;
                    if (nG > 255) nG = 255;
                    if (nB > 255) nB = 255;

                    bitmap.SetPixel(x, y, Color.FromArgb(nR, nG, nB));
                }
            }
            return _bitmap;
        }

        /// <summary>
        /// used to create a deep copy
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new ChannelDifferences(this);
        }
    }
}
